package com.zhangfb95.crosschain.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhangfb95.crosschain.infra.exception.BusinessException;
import com.zhangfb95.crosschain.server.infra.data.mapper.ChainAccountMapper;
import com.zhangfb95.crosschain.server.infra.data.mapper.ChainAccountPropertyMapper;
import com.zhangfb95.crosschain.server.infra.model.mapper.entity.ChainAccount;
import com.zhangfb95.crosschain.server.infra.model.mapper.entity.ChainAccountProperty;
import com.zhangfb95.crosschain.server.service.ChainAccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zhangfubing
 * @since 2023/11/24
 */
@Slf4j
@Service
public class ChainAccountServiceImpl implements ChainAccountService {

    @Autowired
    private ChainAccountMapper chainAccountMapper;
    @Autowired
    private ChainAccountPropertyMapper chainAccountPropertyMapper;

    @Transactional
    @Override
    public void add(ChainAccount chainAccount, Map<String, String> properties) {
        ChainAccount dbChainAccount = getChainAccount(chainAccount.getUserId(),
                chainAccount.getChainPath(),
                chainAccount.getChainIdentity());
        if (dbChainAccount != null) {
            throw new BusinessException("链账户已存在，请检查链路径和链标识");
        }

        // 插入链账户。插入之前先更新是否默认账户
        if (chainAccount.getIsDefault()) {
            chainAccountMapper.updateDefaultToFalse(chainAccount.getUserId(), chainAccount.getChainPath());
        }
        if (chainAccountMapper.insert(chainAccount) < 1) {
            throw new BusinessException("链账户写入失败");
        }

        // 插入链账户属性
        if (!CollectionUtils.isEmpty(properties)) {
            properties.forEach((key, value) -> {
                ChainAccountProperty chainAccountProperty = new ChainAccountProperty()
                        .setChainAccountId(chainAccount.getId())
                        .setPropertyKey(key)
                        .setPropertyValue(value);
                if (chainAccountPropertyMapper.insert(chainAccountProperty) < 1) {
                    throw new BusinessException("链账户属性写入失败");
                }
            });
        }
    }

    @Override
    public void remove(Long userId, String chainPath, String chainIdentity) {
        ChainAccount dbChainAccount = getChainAccount(userId, chainPath, chainIdentity);
        if (dbChainAccount == null) {
            throw new BusinessException("链账户不存在，请检查链路径和链标识");
        }

        // 删除链账户
        chainAccountMapper.deleteById(dbChainAccount.getId());

        // 删除链账户属性
        LambdaQueryWrapper<ChainAccountProperty> propertyQueryWrapper = new LambdaQueryWrapper<>();
        propertyQueryWrapper.eq(ChainAccountProperty::getChainAccountId, dbChainAccount.getId());
        chainAccountPropertyMapper.delete(propertyQueryWrapper);
    }

    @Override
    public void addProperties(Long userId, String chainPath, String chainIdentity, Map<String, String> properties) {
        if (CollectionUtils.isEmpty(properties)) {
            return;
        }

        // 查询链账户
        ChainAccount chainAccount = getChainAccount(userId, chainPath, chainIdentity);
        if (chainAccount == null) {
            throw new BusinessException("链账户不存在，请检查链路径和链标识");
        }

        // 查询链账户属性
        LambdaQueryWrapper<ChainAccountProperty> propertyQueryWrapper = new LambdaQueryWrapper<>();
        propertyQueryWrapper.eq(ChainAccountProperty::getChainAccountId, chainAccount.getId());
        propertyQueryWrapper.in(ChainAccountProperty::getPropertyKey, properties.keySet());
        List<ChainAccountProperty> dbProperties = chainAccountPropertyMapper.selectList(propertyQueryWrapper);
        Map<String, ChainAccountProperty> dbPropertyMap =
                dbProperties.stream().collect(Collectors.toMap(ChainAccountProperty::getPropertyKey, o -> o));

        // 插入或修改链账户属性
        properties.forEach((key, value) -> {
            ChainAccountProperty chainAccountProperty = new ChainAccountProperty()
                    .setChainAccountId(chainAccount.getId())
                    .setPropertyKey(key)
                    .setPropertyValue(value);
            if (!dbPropertyMap.containsKey(key)) {
                chainAccountPropertyMapper.insert(chainAccountProperty);
            } else {
                chainAccountProperty.setId(dbPropertyMap.get(key).getId());
                chainAccountPropertyMapper.updateById(chainAccountProperty);
            }
        });
    }

    @Override
    public void removeProperties(Long userId, String chainPath, String chainIdentity, List<String> propertyKeys) {
        if (CollectionUtils.isEmpty(propertyKeys)) {
            return;
        }

        // 查询链账户
        ChainAccount chainAccount = getChainAccount(userId, chainPath, chainIdentity);
        if (chainAccount == null) {
            throw new BusinessException("链账户不存在，请检查链路径和链标识");
        }

        LambdaQueryWrapper<ChainAccountProperty> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChainAccountProperty::getChainAccountId, chainAccount.getId());
        queryWrapper.in(ChainAccountProperty::getPropertyKey, propertyKeys);
        chainAccountPropertyMapper.delete(queryWrapper);
    }

    @Override
    public ChainAccount getDefaultChainAccount(Long userId, String chainPath) {
        LambdaQueryWrapper<ChainAccount> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChainAccount::getUserId, userId);
        queryWrapper.eq(ChainAccount::getChainPath, chainPath);
        queryWrapper.eq(ChainAccount::getIsDefault, true);
        List<ChainAccount> chainAccounts = chainAccountMapper.selectList(queryWrapper);

        if (CollectionUtils.isEmpty(chainAccounts)) {
            log.warn("not find chainAccount, userId:{}, chainPath:{}", userId, chainPath);
            return null;
        }

        if (chainAccounts.size() > 1) {
            log.warn("find {} chainAccount, userId:{}, chainPath:{}, will fetch first",
                    chainAccounts.size(), userId, chainPath);
        }
        return chainAccounts.get(0);
    }

    @Override
    public List<ChainAccount> listChainAccount(Long userId, String chainPath) {
        LambdaQueryWrapper<ChainAccount> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChainAccount::getUserId, userId);
        if (chainPath != null) {
            queryWrapper.eq(ChainAccount::getChainPath, chainPath);
        }
        return chainAccountMapper.selectList(queryWrapper);
    }

    @Override
    public List<ChainAccountProperty> listChainAccountProperty(Long chainAccountId, List<String> propertyKeys) {
        if (CollectionUtils.isEmpty(propertyKeys)) {
            return Collections.emptyList();
        }

        LambdaQueryWrapper<ChainAccountProperty> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChainAccountProperty::getChainAccountId, chainAccountId);
        queryWrapper.in(ChainAccountProperty::getPropertyKey, propertyKeys);
        return chainAccountPropertyMapper.selectList(queryWrapper);
    }

    @Override
    public List<ChainAccountProperty> listChainAccountProperties(List<Long> chainAccountIds) {
        if (CollectionUtils.isEmpty(chainAccountIds)) {
            return Collections.emptyList();
        }

        LambdaQueryWrapper<ChainAccountProperty> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ChainAccountProperty::getChainAccountId, chainAccountIds);
        return chainAccountPropertyMapper.selectList(queryWrapper);
    }

    private ChainAccount getChainAccount(Long userId, String chainPath, String chainIdentity) {
        LambdaQueryWrapper<ChainAccount> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChainAccount::getUserId, userId);
        queryWrapper.eq(ChainAccount::getChainPath, chainPath);
        queryWrapper.eq(ChainAccount::getChainIdentity, chainIdentity);
        return chainAccountMapper.selectOne(queryWrapper);
    }
}
